class Complie {
    constructor(vm) {
        this.el = this.isElementNode(vm.$el) ? el : document.getElementById(vm.$el);
        this.vm = vm;

        if (this.el) {
            //如果元素存在才开始编译
            //1.先把真是的DOM移入内存中 fragment
            let fragment = this.nodeFragment(this.el);
            //2.编译 提取元素节点 和 文本节点
            this.complie(fragment);
            //3.把编译好的片段放回去
            this.el.appendChild(fragment);
            //释放片段
            fragment = null;
        }
    }

    isElementNode(node) {
        return node.nodeType === 1;
    }

    isDirective(name) {
        return name.includes('v-')
    }


    nodeFragment(el) {
        let fragment = document.createDocumentFragment();
        let firstChild;
        while (el.firstChild) {
            fragment.appendChild(el.firstChild);
        }
        return fragment;//内存中的节点
    }

    complieElement(node) {
        //判断v-model
        let attrs = node.attributes;//取出属性节点
        Array.from(attrs).forEach(attr => {
            let attrName = attr.name;
            if (this.isDirective(attrName)) {

                //取对应值放到节点中
                let expr = attr.value;
                let type = attrName.slice(2);
                CompileUtil[type](node, this.vm, expr);
            }
        })
    }

    complieText(node) {
        let text = node.textContent;
        let reg = /\{\{([^}]+)\}\}/g;
        if (reg.test(text)) {
            //node this.vm.$data
            CompileUtil['text'](node, this.vm, text);
        }
    }

    complie(fragment) {
        let childNodes = fragment.childNodes;//遍历子节点
        // 转换成数组并循环判断每一个节点的类型
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) {
                // 递归编译子节点
                this.complie(node);

                // 编译元素节点的方法
                this.complieElement(node);
            } else {
                // 编译文本节点的方法
                this.complieText(node);
            }
        });
    }
}

CompileUtil = {
    getVal(vm, expr) { // 获取实例上对应的数据 针对深层嵌套
        expr = expr.split('.'); // message.a => [message,a]
        return expr.reduce((prev, next) => { // vm.$data.a
            return prev[next];
        }, vm.$data);
    },
    getTextVal(vm, expr) { // 获取编译文本后的结果
        console.log('expr2', expr);
        return expr.replace(/\{\{([^}]+)\}\}/g, (...args) => {
            return this.getVal(vm, args[1]);
        })
    },
    setVal(vm, expr, value) {
        expr = expr.split('.'); // message.a => [message,a]
        return expr.reduce((prev, next, curIndex) => { // vm.$data.a
            if (curIndex === expr.length -1 ) {
                return prev[next] = value;
            }
            return prev[next];
        }, vm.$data);
    },
    text(node, vm, expr) { //文本处理
        let updateFn = this.updater['textUpdater'];
        let value = this.getTextVal(vm, expr);

        expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
            new Wather(vm, arguments[1], () => {
                // 解析时遇到了模板中需要替换为数据值的变量时，应该添加一个观察者
                // 当变量重新赋值时，调用更新值节点到 Dom 的方法
                updateFn && updateFn(node, this.getTextVal(vm, expr))
            })
        })
        updateFn && updateFn(node, value)
    },
    model(node, vm, expr) {
        let updateFn = this.updater['modelUpdater'];
        //开始观察
        new Wather(vm, expr, (newVal) => {
            //当值发生变化的时候
            updateFn && updateFn(node, newVal);
        });
        node.addEventListener('input', (e) => {
            let newVal = e.target.value;
            this.setVal(vm, expr, newVal);
        });
        updateFn && updateFn(node, this.getVal(vm, expr));
    },
    updater: {
        textUpdater(node, value) {//文本更新
            node.textContent = value;
        },
        modelUpdater(node, value) {//输入框更新
            node.value = value;
        }
    }
}